package filetools

import (
	"errors"
	"fmt"
	"github.com/gogf/gf/errors/gerror"
	"github.com/h2non/filetype"
	"github.com/shirou/gopsutil/v3/disk"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/core"
	"gitlab.local/DO-module/new-filemanage-module/src/core/fileoperate"
	"gitlab.local/golibrary/utils"
	"io/fs"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"sync"
	"syscall"
	"time"
)

var VolumeRegexP *regexp.Regexp = regexp.MustCompile(`^/Volume\d+($|/)`)

// SearchFiles 新的文件搜索
func (f *FileTools) SearchFiles(skipHide bool) ([]fileoperate.FoldersInfo, error) {

	data := make([]fileoperate.FoldersInfo, 0)
	if f.FileTool.Location == "" {
		return data, errors.New("location is empty")
	}
	stat, err := os.Stat(f.FileTool.Location)
	if err != nil {
		return data, gerror.Wrap(err, "SearchFiles")
	}
	if !stat.IsDir() {
		return data, fmt.Errorf(`%s not a directory`, f.FileTool.Location)
	}
	var stDate, enData time.Time
	//解析搜索范围时间类型
	if f.FileTool.Time != "" && f.FileTool.Date != "" {
		stDate, enData, err = f.analyzeTimeType(f.FileTool.Time, f.FileTool.Date)
		if err != nil {
			return data, gerror.Wrap(err, "SearchFiles1")
		}
	}

	var (
		dirs  []SearchFileInfo
		files []SearchFileInfo
	)
	reg := regexp.MustCompile(`^(?:/home/[^/]+/\.safe|/Volume\d+/User/[^/]+/\.safe)(/+)?$`)
	fsys := os.DirFS(f.FileTool.Location)

	fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if res := f.newSearchFiles(path, d, stDate, enData, f.FileTool, skipHide, *reg); res != nil {
			if res.IsDir {
				dirs = append(dirs, *res)
			} else {
				files = append(files, *res)
			}
		}
		return nil
	})

	if f.FileTool.Rule != "" {
		//对文件和文件夹分别排序
		var wg sync.WaitGroup
		wg.Add(2)

		go func() {
			defer wg.Done()
			dirs = f.Sort(dirs, f.FileTool.Rule, f.FileTool.Order)
		}()

		go func() {
			defer wg.Done()
			files = f.Sort(files, f.FileTool.Rule, f.FileTool.Order)
		}()

		wg.Wait()
	}

	dirs = append(dirs, files...)
	data = f.getFolder(dirs)

	return data, nil
}

func (f *FileTools) getFolder(folders []SearchFileInfo) []fileoperate.FoldersInfo {
	result := make([]fileoperate.FoldersInfo, 0)
	if len(folders) == 0 {
		return result
	}
	numWorkers := int(math.Ceil(float64(len(folders)) / 50))

	var wg sync.WaitGroup
	wg.Add(numWorkers)

	// 计算每个子切片的大小
	chunkSize := len(folders) / numWorkers
	var mu sync.Mutex

	for i := 0; i < numWorkers; i++ {
		start := i * chunkSize
		end := start + chunkSize

		// 处理最后一个子切片时，调整结束索引
		if i == numWorkers-1 {
			end = len(folders)
		}
		go func(slice []SearchFileInfo) {
			defer wg.Done()
			for _, v := range slice {
				folder, err := f.GetFolder(v.Path, v)
				if err != nil {
					continue
				}
				if err == nil {
					mu.Lock()
					result = append(result, *folder)
					mu.Unlock()
				}
			}
		}(folders[start:end])
	}

	wg.Wait()
	return result
}

// 解析搜索范围时间类型
// return 开始时间戳 结束时间戳
func (f *FileTools) analyzeTimeType(t, date string) (time.Time, time.Time, error) {
	now := time.Now()
	switch t {
	case "today":
		return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local),
			time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local),
			nil
	case "yesterday":
		now = now.AddDate(0, 0, -1)
		return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local),
			time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local),
			nil
	case "thisWeek":
		offset := int(time.Monday - now.Weekday())
		if offset > 0 {
			offset = -6
		}
		now = now.AddDate(0, 0, offset)
		st := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
		now = now.AddDate(0, 0, +6)
		en := time.Date(now.Year(), now.Month(), now.Day(), 23, 23, 23, 0, time.Local)
		return st, en, nil
	case "thisMonth":
		st := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)
		en := st.AddDate(0, 1, 0).Add(-time.Second)
		return st, en, nil
	case "thisYear":
		st := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.Local)
		en := st.AddDate(1, 0, 0).Add(-time.Second)
		return st, en, nil
	case "customize":
		location, err := time.ParseInLocation("2006-01-02", date, time.Local)
		if err != nil {
			return time.Time{}, time.Time{}, gerror.Wrap(err, "analyzeTimeType")
		}
		return time.Date(location.Year(), location.Month(), location.Day(), 0, 0, 0, 0, time.Local),
			time.Date(location.Year(), location.Month(), location.Day(), 23, 59, 59, 0, time.Local),
			nil
	}
	return time.Time{}, time.Time{}, nil
}

func (f *FileTools) newSearchFiles(path string, d fs.DirEntry, stDate, enData time.Time, fT FileTool, skipHide bool, reg regexp.Regexp) *SearchFileInfo {
	if skipHide && strings.HasPrefix(d.Name(), ".") {
		//跳过隐藏文件
		return nil
	}
	//跳过指定文件或文件夹
	if strings.HasPrefix(d.Name(), constant.IgnoreFilePre) {
		return nil
	}
	//排除保险箱目录及以下文件夹或文件夹
	if reg.MatchString(path) {
		return nil
	}

	//keyWord
	if !f.keyWord(d.Name(), fT.KeyWord, fT.CaseSensitive) {
		return nil
	}
	//fileExt
	if !f.fileExt(d.Name(), fT.Ext) {
		return nil
	}

	info, err := d.Info()
	if err != nil {
		return nil
	}

	//fileSize
	if !f.fileSize(info.Size(), fT.StSize, fT.EnSize) {
		return nil
	}
	//timeType
	if !f.timeType(info, fT.TimeType, stDate, enData) {
		return nil
	}
	//fileType
	if !f.fileType(path, fT.Type, info) {
		return nil
	}

	return &SearchFileInfo{
		Path:  path,
		Name:  d.Name(),
		MTile: info.ModTime(),
		Sys:   info.Sys(),
		Size:  info.Size(),
		IsDir: d.IsDir(),
	}
}

// Sort 排序
func (f *FileTools) Sort(dirs []SearchFileInfo, rule fileoperate.OrderRule, order bool) []SearchFileInfo {

	sort.SliceStable(dirs, func(i, j int) bool {
		switch rule {
		case fileoperate.Time:
			if dirs[i].MTile.After(dirs[j].MTile) {
				if order { //a>b 为降序时触发
					return true
				}
			} else {
				if !order { //a<b 为升序时触发
					return true
				}
			}
		case fileoperate.Size:
			var a, b int64
			if dirs[i].IsDir {
				a, _ = f.shareGetter.FolderSize(dirs[i].Path, nil)
			} else {
				a = dirs[i].Size
			}
			if dirs[j].IsDir {
				b, _ = f.shareGetter.FolderSize(dirs[j].Path, nil)
			} else {
				b = dirs[j].Size
			}
			if a > b {
				if order { //a>b 为降序时触发
					return true
				}
			} else {
				if !order { //a<b 为升序时触发
					return true
				}
			}
		case fileoperate.Nature:
			if dirs[i].Name > dirs[j].Name {
				if order { //a>b 为降序时触发
					return true
				}
			} else {
				if !order { //a<b 为升序时触发
					return true
				}
			}
		}

		return false
	})

	return dirs
}

// keyWord 搜索关键字
func (f *FileTools) keyWord(name string, keyWord string, caseSensitive bool) bool {
	if keyWord == "" {
		return true
	}
	if caseSensitive {
		return strings.Contains(name, keyWord)
	}
	return strings.Contains(strings.ToUpper(name), strings.ToUpper(keyWord))
}

func (f *FileTools) fileExt(name, ext string) bool {
	if ext == "" {
		return true
	}
	return strings.HasSuffix(name, "."+ext)
}

func (f *FileTools) fileSize(total, stSize, enSize int64) bool {
	if enSize == 0 {
		return total >= stSize
	}
	return total >= stSize && total <= enSize
}

func (f *FileTools) fileType(path, fileType string, info fs.FileInfo) bool {
	//文件类型(为空所有,folder,text,documents,image,video,audio,archive)
	if fileType == "" {
		return true
	}
	if fileType == "folder" {
		return info.IsDir()
	}
	if fileType == "text" {
		return strings.HasSuffix(info.Name(), ".txt")
	}
	var size int64 = 32774 //一般文件只需前262个字符，iso类大文件可能需要更大
	if info.Size() < size {
		size = info.Size()
	}
	file, err := os.Open(path)
	if err != nil {
		return false
	}
	defer file.Close()
	buf := make([]byte, size)
	if _, err = file.Read(buf); err != nil {
		return false
	}
	switch fileType {
	case "documents":
		return filetype.IsDocument(buf)
	case "image":
		return filetype.IsImage(buf)
	case "video":
		return filetype.IsVideo(buf)
	case "audio":
		return filetype.IsAudio(buf)
	case "archive":
		return filetype.IsArchive(buf)
	}
	return false
}

func (f *FileTools) timeType(info fs.FileInfo, timeType string, stDate, enData time.Time) bool {
	if timeType == "" {
		return true
	}
	var at time.Time
	switch timeType {
	case "modifyTime":
		at = info.ModTime()
	case "accessTime":
		t := info.Sys().(*syscall.Stat_t)
		at = time.Unix(t.Atim.Sec, t.Atim.Nsec)
	}
	if !stDate.IsZero() && at.Before(stDate) { //小于开始时间
		return false
	}
	if !enData.IsZero() && at.After(enData) { //大于结束时间
		return false
	}
	return true
}

// GetFolder 获取文件或者文件夹基本信息
func (f *FileTools) GetFolder(path string, info SearchFileInfo) (*fileoperate.FoldersInfo, error) {
	data := &fileoperate.FoldersInfo{}
	dateLayout, timeLayout := core.New().GetTimeFormat()
	layOut := fmt.Sprintf("%s %s", dateLayout, timeLayout)
	// 转换为系统相关的文件信息
	sys := info.Sys.(*syscall.Stat_t)

	// 获取文件创建时间
	createTime := time.Unix(sys.Ctim.Unix())
	if !createTime.IsZero() {
		data.CTime = createTime.Format(layOut)
	}
	data.Name = info.Name
	data.Path = path
	data.ATime = time.Unix(sys.Atim.Sec, sys.Atim.Nsec).Format(layOut)
	data.MTime = info.MTile.Format(layOut)
	data.Volume = strings.TrimPrefix(strings.TrimSuffix(VolumeRegexP.FindString(path), "/"), "/")
	if data.Volume == "" {
		data.Volume = f.GetHomeVolume()
	}
	if info.IsDir {
		data.FType = utils.Folder
	} else {
		if len(utils.FileClassifyList) == 0 {
			utils.FileClassifyList = map[string]utils.FType{
				"oexe": utils.Oexe,                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   //快捷方式
				"svg":  utils.Image, "jpg": utils.Image, "jpeg": utils.Image, "png": utils.Image, "gif": utils.Image, "webp": utils.Image, "raf": utils.Image, "crw": utils.Image, "cr2": utils.Image, "kdc": utils.Image, "mrw": utils.Image, "nef": utils.Image, "orf": utils.Image, "dng": utils.Image, "ptx": utils.Image, "pef": utils.Image, "arw": utils.Image, "x3f": utils.Image, "rw2": utils.Image, "tif": utils.Image, "tiff": utils.Image, "bmp": utils.Image, "heic": utils.Image, "heif": utils.Image, "jxr": utils.Image, "psd": utils.Image, "ico": utils.Image, "dwg": utils.Image, //图像
				"mp4": utils.Video, "m4v": utils.Video, "mkv": utils.Video, "webm": utils.Video, "mov": utils.Video, "avi": utils.Video, "wmv": utils.Video, "mpg": utils.Video, "flv": utils.Video, "3gp": utils.Video, //视频
				"mid": utils.Audio, "mp3": utils.Audio, "m4a": utils.Audio, "ogg": utils.Audio, "flac": utils.Audio, "wav": utils.Audio, "amr": utils.Audio, "aac": utils.Audio, "aiff": utils.Audio, //音频
			}
		}

		//获取文件真实类型(直接获取文件后缀)
		data.FRealType = strings.ToLower(strings.TrimPrefix(filepath.Ext(path), "."))
		data.FType, _ = extFileClassify(data.FRealType)
		data.Size = info.Size
		data.SizeFriendly = utils.FileSizeFriendly(data.Size)
		if data.FType == utils.Oexe {
			d, err := utils.GetOexeDate(path)
			if err == nil {
				data.Link = d.Link
				data.Icon = d.Icon
			}
		}
	}

	return data, nil
}

func (f *FileTools) GetHomeVolume() string {
	homeVolume := ""
	df, err := disk.Partitions(false)
	if err != nil {
		return ""
	}

	//获取home挂载卷
	for _, v := range df {
		if v.Mountpoint == "/home" {
			homeVolume = v.Device
			break
		}
	}
	for _, v := range df {
		if v.Device == homeVolume {
			homeVolume = v.Mountpoint
			break
		}
	}
	return strings.Trim(homeVolume, "/")
}

func extFileClassify(ext string) (utils.FType, error) {
	tmp := strings.ToLower(ext)
	fType, b := utils.FileClassifyList[tmp]
	if b {
		return fType, nil
	}
	return utils.File, nil
}
